/*  This program is free software; you can redistribute it and/or modify it
    under the terms of the GNU Lesser General Public License as published by
    the Free Software Foundation; either version 2.1 of the License, or (at
    your option) any later version.
    For more details, see the GNU Lesser General Public License (www.fsf.org
    or the COPYING file somewhere in the package)
 */

/*! @file test/Bundles.cc
    @brief Class Bundles definition.
    @author @ref Guillaume_Terrissol
    @date 15th February 2010 - 16th October 2014
    @note This file is distributed under the LGPL license.
    Refer to the file COPYING (or http://www.fsf.org) for more information.
 */

#include <algorithm>
#include <stdexcept>
#include <unistd.h>

#include <cppunit/extensions/HelperMacros.h>

#include <BundleMgr.hh>
#include <BundleInstallerService.hh>
#include <Utils.hh>

#include "Utils.hh"

namespace CHK
{
//------------------------------------------------------------------------------
//                                  Test Class
//------------------------------------------------------------------------------

    /*! @brief Standard checks for Bundle management.
        @version 0.9
     */
    class Bundles : public CppUnit::TestFixture
    {
#ifndef NOT_FOR_DOXYGEN
        // Tests registering
        CPPUNIT_TEST_SUITE(Bundles);
        CPPUNIT_TEST(loadingSingleBundle);
        CPPUNIT_TEST(loadingSeveralBundles);
        CPPUNIT_TEST(loadingSingleBundleSeveralVersions);
        CPPUNIT_TEST(loadingSeveralBundlesSeveralVersions);
        CPPUNIT_TEST(loadingExplicitPlatform);
        CPPUNIT_TEST(loadingPartialPlatform);
        CPPUNIT_TEST(checkingDependencies);
        CPPUNIT_TEST(checkingMissingDependencies);
        CPPUNIT_TEST(checkingDependenciesMissingVersion);
        CPPUNIT_TEST(checkingWrongDependencies);
        CPPUNIT_TEST(checkingUnloading);
        CPPUNIT_TEST(checkingDoubleStartAndFinalize);
        CPPUNIT_TEST(checkingSeveralManagers);
        CPPUNIT_TEST(checkingVerboseOff);
        CPPUNIT_TEST(checkingPathChange);
        CPPUNIT_TEST_EXCEPTION(checkingInvalidBundlePath, OSGi::IOError);
        CPPUNIT_TEST_SUITE_END();
#endif  // NOT_FOR_DOXYGEN
    public:
        //! @name "Interface"
        //@{
virtual         ~Bundles() = default;                   //!< Destructor.
virtual void    setUp() override;                       //!< Preparation.
        //@}

    private:
        //! @name Testing
        //@{
        void    loadingSingleBundle();                  //!< ... single bundle loading.
        void    loadingSeveralBundles();                //!< ... several bundles loading.
        void    loadingSingleBundleSeveralVersions();   //!< ... single bundle loading, several versions.
        void    loadingSeveralBundlesSeveralVersions(); //!< ... several bundles loading, several versions.
        void    loadingExplicitPlatform();              //!< ... explicit platform in bundlespec.
        void    loadingPartialPlatform();               //!< ... partial platform in bundlespec.
        void    checkingDependencies();                 //!< ... dependencies.
        void    checkingMissingDependencies();          //!< ... missing dependencies.
        void    checkingDependenciesMissingVersion();   //!< ... missing dependencies based on version.
        void    checkingWrongDependencies();            //!< 
        void    checkingUnloading();                    //!< ... bundles unloading.
        void    checkingDoubleStartAndFinalize();       //!< ... "hot" start.
        void    checkingSeveralManagers();              //!< ... concurrent bundle managers.
        void    checkingVerboseOff();                   //!< ... verbosity.
        void    checkingPathChange();                   //!< ... path change while running.
        void    checkingInvalidBundlePath();            //!< ... error management for invalid bundle path.
        //@}
    };


    CPPUNIT_TEST_SUITE_NAMED_REGISTRATION(Bundles, "Standard tests");


//------------------------------------------------------------------------------
//                                 Helper Macros
//------------------------------------------------------------------------------


#define CHECK_BUNDLE(pMgr, pName, pVersion)                                             \
    {                                                                                   \
        std::string lName{pName};                                                       \
        std::string lVersion{pVersion};                                                 \
                                                                                        \
        auto        lBndl   = pMgr->context()->findBundle(lName);                       \
        CPPUNIT_ASSERT_MESSAGE((std::string("Missing bundle : ") + lName).c_str(),      \
                               lBndl);                                                  \
                                                                                        \
        CPPUNIT_ASSERT_MESSAGE((std::string("Inactive bundle : ") + lName).c_str(),     \
                               lBndl->state() == "active");                             \
                                                                                        \
        if (!lVersion.empty())                                                          \
        {                                                                               \
            CPPUNIT_ASSERT_MESSAGE("Incorrect bundle version",                          \
                                   lBndl->version() == lVersion);                       \
        }                                                                               \
                                                                                        \
        const auto* lProps  = lBndl->properties();                                      \
        CPPUNIT_ASSERT_MESSAGE((std::string("Missing properties : ") + lName).c_str(),  \
                               lProps != nullptr);                                      \
                                                                                        \
        CPPUNIT_ASSERT_MESSAGE("Missing property",                                      \
                               lProps->has("comment"));                                 \
        CPPUNIT_ASSERT_MESSAGE("Invalid property value",                                \
                               lProps->get("comment") == (lName + " : oneself"));       \
    }


#define CHECK_UNRESOLVED_BUNDLE(pMgr, pName)                                        \
    {                                                                               \
        std::string lName{pName};                                                   \
                                                                                    \
        auto    lBndl   = pMgr->context()->findBundle(lName);                       \
        CPPUNIT_ASSERT_MESSAGE((std::string("Missing bundle : ") + lName).c_str(),  \
                               lBndl);                                              \
                                                                                    \
        CPPUNIT_ASSERT_MESSAGE((std::string("Resolved bundle : ") + lName).c_str(), \
                               lBndl->state() == "installed");                      \
    }


#define CHECK_INACTIVE_BUNDLE(pMgr, pName, pVersion)                                \
    {                                                                               \
        std::string lName{pName};                                                   \
        std::string lVersion{pVersion};                                             \
                                                                                    \
        auto    lBndl   = pMgr->context()->findBundle(lName);                       \
        CPPUNIT_ASSERT_MESSAGE((std::string("Missing bundle : ") + lName).c_str(),  \
                               lBndl);                                              \
                                                                                    \
        CPPUNIT_ASSERT_MESSAGE((std::string("Resolved bundle : ") + lName).c_str(), \
                               lBndl->state() == "resolved");                       \
                                                                                    \
        if (!lVersion.empty())                                                      \
        {                                                                           \
            CPPUNIT_ASSERT_MESSAGE("Incorrect bundle version",                      \
                                   lBndl->version() == lVersion);                   \
        }                                                                           \
    }


//------------------------------------------------------------------------------
//                                  "Interface"
//------------------------------------------------------------------------------

    /*! Cleanup between tests.
     */
    void Bundles::setUp()
    {
        removeBundles();
    }


//------------------------------------------------------------------------------
//                                     Tests
//------------------------------------------------------------------------------

    /*! @test @par Checking a single bundle loading.
        The full life cycle is tested, for a single bundle.
     */
    void Bundles::loadingSingleBundle()
    {
        installBundle("fr.osgi.test.alone_1.0.0.bndl");

        auto    lMgr = OSGi::BundleMgr::make({ "cleanCache", "verbose", "out=string", "err=string" });

        lMgr->loadBundles();
        lMgr->startBundles();

        CHECK_BUNDLE(lMgr, "fr.osgi.test.alone", "1.0.0")

        lMgr->finalize();
        removeBundles();

        CPPUNIT_ASSERT_MESSAGE("Unexpected message on output log",
                               dynamic_cast<std::ostringstream&>(lMgr->context()->out()).str() ==
                               "Bundle bundles/fr.osgi.test.alone_1.0.0.bndl installed\n"
                               "Bundle fr.osgi.test.alone resolved\n"
                               "Starting ALONE::Activator\n"
                               "Stopping ALONE::Activator\n");
        CPPUNIT_ASSERT_MESSAGE("Unexpected message on error log",
                               dynamic_cast<std::ostringstream&>(lMgr->context()->err()).str().empty());
    }


    /*! @test @par Checking several bundles loading.
        The handled bundles haven't any dependency; they are sorted alphabetically.@n
        Order is checked for every step of their life cycle.
     */
    void Bundles::loadingSeveralBundles()
    {
        installBundle("fr.osgi.test.1st_1.2.3.bndl");
        installBundle("fr.osgi.test.2nd_2.2.4.bndl");
        installBundle("fr.osgi.test.3rd_1.9.0.bndl");

        auto    lMgr = OSGi::BundleMgr::make({ "cleanCache", "verbose", "out=string", "err=string" });
        lMgr->loadBundles();
        lMgr->startBundles();

        CHECK_BUNDLE(lMgr, "fr.osgi.test.1st", "1.2.3")
        CHECK_BUNDLE(lMgr, "fr.osgi.test.2nd", "2.2.4")
        CHECK_BUNDLE(lMgr, "fr.osgi.test.3rd", "1.9.0")

        lMgr->finalize();
        removeBundles();

        CPPUNIT_ASSERT_MESSAGE("Unexpected message on output log",
                               dynamic_cast<std::ostringstream&>(lMgr->context()->out()).str() ==
                               "Bundle bundles/fr.osgi.test.1st_1.2.3.bndl installed\n"
                               "Bundle bundles/fr.osgi.test.2nd_2.2.4.bndl installed\n"
                               "Bundle bundles/fr.osgi.test.3rd_1.9.0.bndl installed\n"
                               "Bundle fr.osgi.test.1st resolved\n"
                               "Bundle fr.osgi.test.2nd resolved\n"
                               "Bundle fr.osgi.test.3rd resolved\n"
                               "Starting N1ST::Activator\n"
                               "Starting N2ND::Activator\n"
                               "Starting N3RD::Activator\n"
                               "Stopping N3RD::Activator\n"
                               "Stopping N2ND::Activator\n"
                               "Stopping N1ST::Activator\n");
        CPPUNIT_ASSERT_MESSAGE("Unexpected message on error log",
                               dynamic_cast<std::ostringstream&>(lMgr->context()->err()).str().empty());
    }


    /*! @test @par Checking a single bundle loading (selecting the correct version).
        3 versions of the bundle are loaded. Only the last one (with the higher version) is installed.
     */
    void Bundles::loadingSingleBundleSeveralVersions()
    {
        installBundle("fr.osgi.test.alone_1.0.0.bndl");
        installBundle("fr.osgi.test.alone_1.2.4.bndl");
        installBundle("fr.osgi.test.alone_1.3.2.bndl");

        auto    lMgr = OSGi::BundleMgr::make({ "cleanCache", "verbose", "out=string", "err=string" });
        lMgr->loadBundles();
        lMgr->startBundles();

        CHECK_BUNDLE(lMgr, "fr.osgi.test.alone", "1.3.2")

        lMgr->finalize();
        removeBundles();

        CPPUNIT_ASSERT_MESSAGE("Unexpected message on output log",
                               dynamic_cast<std::ostringstream&>(lMgr->context()->out()).str() ==
                               "Bundle bundles/fr.osgi.test.alone_1.3.2.bndl installed\n"
                               "Bundle fr.osgi.test.alone resolved\n"
                               "Starting ALONE::Activator\n"
                               "Stopping ALONE::Activator\n");
        CPPUNIT_ASSERT_MESSAGE("Unexpected message on error log",
                               dynamic_cast<std::ostringstream&>(lMgr->context()->err()).str().empty());
    }


    /*! @test @par Checking several bundles loading (selecting the correct versions).
        Various versions of several bundles are loaded. Only the bundles with the higher versions are installed.
     */
    void Bundles::loadingSeveralBundlesSeveralVersions()
    {
        installBundle("fr.osgi.test.1st_1.2.3.bndl");
        installBundle("fr.osgi.test.1st_2.1.3.bndl");
        installBundle("fr.osgi.test.2nd_1.5.3.bndl");
        installBundle("fr.osgi.test.2nd_2.2.4.bndl");
        installBundle("fr.osgi.test.3rd_1.0.9.bndl");
        installBundle("fr.osgi.test.3rd_1.9.0.bndl");

        auto    lMgr = OSGi::BundleMgr::make({ "cleanCache", "verbose", "out=string", "err=string" });
        lMgr->loadBundles();
        lMgr->startBundles();

        CHECK_BUNDLE(lMgr, "fr.osgi.test.1st", "2.1.3")
        CHECK_BUNDLE(lMgr, "fr.osgi.test.2nd", "2.2.4")
        CHECK_BUNDLE(lMgr, "fr.osgi.test.3rd", "1.9.0")

        lMgr->finalize();
        removeBundles();

        CPPUNIT_ASSERT_MESSAGE("Unexpected message on output log",
                               dynamic_cast<std::ostringstream&>(lMgr->context()->out()).str() ==
                               "Bundle bundles/fr.osgi.test.1st_2.1.3.bndl installed\n"
                               "Bundle bundles/fr.osgi.test.2nd_2.2.4.bndl installed\n"
                               "Bundle bundles/fr.osgi.test.3rd_1.9.0.bndl installed\n"
                               "Bundle fr.osgi.test.1st resolved\n"
                               "Bundle fr.osgi.test.2nd resolved\n"
                               "Bundle fr.osgi.test.3rd resolved\n"
                               "Starting N1ST::Activator\n"
                               "Starting N2ND::Activator\n"
                               "Starting N3RD::Activator\n"
                               "Stopping N3RD::Activator\n"
                               "Stopping N2ND::Activator\n"
                               "Stopping N1ST::Activator\n");
        CPPUNIT_ASSERT_MESSAGE("Unexpected message on error log",
                               dynamic_cast<std::ostringstream&>(lMgr->context()->err()).str().empty());
    }


    /*! @test @par Explicit platform checking.
        For both GNU/Linux, and Windows, checks that only the requested library has been packed into the bundle, and
        installed into the cache.
     */
    void Bundles::loadingExplicitPlatform()
    {
        {
            installBundle("fr.osgi.test.platform_1.0.0.bndl");

            auto    lMgr = OSGi::BundleMgr::make({ "cleanCache", "verbose", "out=string", "err=string" });
            lMgr->loadBundles();

            std::string lRighteous  = lMgr->context()->cachePath()     + "/fr.osgi.test.platform";
            CPPUNIT_ASSERT_MESSAGE("Righteous library not installed",
                                   std::fstream((lRighteous + POSTFIX".so" ).c_str(), std::fstream::in).is_open() ||
                                   std::fstream((lRighteous + POSTFIX".dll").c_str(), std::fstream::in).is_open());

            std::string lErroneous  = lMgr->context()->temporaryPath() + "/fr.osgi.test.platform/bin/";
            CPPUNIT_ASSERT_MESSAGE("Erroneous library installed",
                                   !std::fstream((lErroneous +  "Linus/i686/fr.osgi.test.platform" POSTFIX".so" ).c_str(), std::fstream::in).is_open() &&
                                   !std::fstream((lErroneous + "Window/IA32/fr.osgi.test.platform" POSTFIX".dll").c_str(), std::fstream::in).is_open());

            lMgr->startBundles();
            CHECK_BUNDLE(lMgr, "fr.osgi.test.platform", "1.0.0")

            lMgr->finalize();
            removeBundles();

            CPPUNIT_ASSERT_MESSAGE("Unexpected message on output log",
                                   dynamic_cast<std::ostringstream&>(lMgr->context()->out()).str() ==
                                   "Bundle bundles/fr.osgi.test.platform_1.0.0.bndl installed\n"
                                   "Bundle fr.osgi.test.platform resolved\n"
                                   "Starting PLATFORM::Activator\n"
                                   "Stopping PLATFORM::Activator\n");
            CPPUNIT_ASSERT_MESSAGE("Unexpected message on error log",
                                   dynamic_cast<std::ostringstream&>(lMgr->context()->err()).str().empty());
        }
        {
            installBundle("fr.osgi.test.platform_1.1.0.bndl");
            auto    lMgr = OSGi::BundleMgr::make({ "cleanCache", "verbose", "out=string", "err=string" });

            lMgr->loadBundles();

            std::string lRighteous  = lMgr->context()->cachePath()     + "/fr.osgi.test.platform";
            CPPUNIT_ASSERT_MESSAGE("Righteous library installed",
                                   !std::fstream((lRighteous + POSTFIX".so" ).c_str(), std::fstream::in).is_open() &&
                                   !std::fstream((lRighteous + POSTFIX".dll").c_str(), std::fstream::in).is_open());

            std::string lErroneous  = lMgr->context()->temporaryPath() + "/fr.osgi.test.platform/bin/";
            CPPUNIT_ASSERT_MESSAGE("Erroneous library not installed",
                                   std::fstream((lErroneous +  "Linus/i686/fr.osgi.test.platform" POSTFIX".so" ).c_str(), std::fstream::in).is_open() ||
                                   std::fstream((lErroneous + "Window/IA32/fr.osgi.test.platform" POSTFIX".dll").c_str(), std::fstream::in).is_open());

            lMgr->startBundles();

            lMgr->finalize();
            removeBundles();

            CPPUNIT_ASSERT_MESSAGE("Unexpected message on output log",
                                   dynamic_cast<std::ostringstream&>(lMgr->context()->out()).str() ==
                                   "Bundle bundles/fr.osgi.test.platform_1.1.0.bndl installed\n"
                                   "Bundle fr.osgi.test.platform resolved\n");
            std::string lErrLog = dynamic_cast<std::ostringstream&>(lMgr->context()->err()).str();
            CPPUNIT_ASSERT_MESSAGE("Unexpected message on error log",
                                   lErrLog.find("Unabled to load cache/fr.osgi.test.platform" POSTFIX".") == 0);
            CPPUNIT_ASSERT_MESSAGE("Too many messages on error log",
                                   std::count(begin(lErrLog), end(lErrLog), '\n') == 2);
        }
    }


    /*! @test @par Checking partial platform specified in bundlespec
        Checks that :
        - library with only OS specified is correctly loaded,
        - library with most specific information is loaded.
     */
    void Bundles::loadingPartialPlatform()
    {
        {
            installBundle("fr.osgi.test.platform_2.0.0.bndl");

            auto    lMgr = OSGi::BundleMgr::make({ "cleanCache", "verbose", "out=string", "err=string" });
            lMgr->loadBundles();
            lMgr->startBundles();

            CHECK_BUNDLE(lMgr, "fr.osgi.test.platform", "2.0.0")

            lMgr->finalize();
            removeBundles();

            CPPUNIT_ASSERT_MESSAGE("Unexpected message on output log",
                                   dynamic_cast<std::ostringstream&>(lMgr->context()->out()).str() ==
                                   "Bundle bundles/fr.osgi.test.platform_2.0.0.bndl installed\n"
                                   "Bundle fr.osgi.test.platform resolved\n"
                                   "Starting PLATFORM::Activator\n"
                                   "Stopping PLATFORM::Activator\n");
            CPPUNIT_ASSERT_MESSAGE("Unexpected message on error log",
                                   dynamic_cast<std::ostringstream&>(lMgr->context()->err()).str().empty());
        }
        {
            installBundle("fr.osgi.test.platform_2.1.0.bndl");

            auto    lMgr = OSGi::BundleMgr::make({ "cleanCache", "verbose", "out=string", "err=string" });
            lMgr->loadBundles();
            lMgr->startBundles();

            CHECK_BUNDLE(lMgr, "fr.osgi.test.platform", "2.1.0")

            lMgr->finalize();
            removeBundles();

            CPPUNIT_ASSERT_MESSAGE("Unexpected message on output log",
                                   dynamic_cast<std::ostringstream&>(lMgr->context()->out()).str() ==
                                   "Bundle bundles/fr.osgi.test.platform_2.1.0.bndl installed\n"
                                   "Bundle fr.osgi.test.platform resolved\n"
                                   "Starting PLATFORMS::Activator\n"
                                   "Stopping PLATFORMS::Activator\n");
            CPPUNIT_ASSERT_MESSAGE("Unexpected message on error log",
                                   dynamic_cast<std::ostringstream&>(lMgr->context()->err()).str().empty());
        }
    }


    /*! @test @par Checking loading order, base on dependencies.
        A hierarchy (based of dependencies) of bundles is installed.@n
        Order is checked for every step of their life cycle.
     */
    void Bundles::checkingDependencies()
    {
        installBundle("fr.osgi.test.root_1.0.0.bndl");
        installBundle("fr.osgi.test.needroot_1.0.0.bndl");
        installBundle("fr.osgi.test.dep2ndlevel_1.0.0.bndl");

        auto    lMgr = OSGi::BundleMgr::make({ "cleanCache", "verbose", "out=string", "err=string" });
        lMgr->loadBundles();
        lMgr->startBundles();

        CHECK_BUNDLE(lMgr, "fr.osgi.test.root",        "1.0.0")
        CHECK_BUNDLE(lMgr, "fr.osgi.test.needroot",    "1.0.0")
        CHECK_BUNDLE(lMgr, "fr.osgi.test.dep2ndlevel", "1.0.0")

        lMgr->finalize();
        removeBundles();

        CPPUNIT_ASSERT_MESSAGE("Unexpected message on output log",
                               dynamic_cast<std::ostringstream&>(lMgr->context()->out()).str() ==
                               "Bundle bundles/fr.osgi.test.dep2ndlevel_1.0.0.bndl installed\n"
                               "Bundle bundles/fr.osgi.test.needroot_1.0.0.bndl installed\n"
                               "Bundle bundles/fr.osgi.test.root_1.0.0.bndl installed\n"
                               "Bundle fr.osgi.test.dep2ndlevel resolved\n" // The others two have been resolved automatically.
                               "Starting ROOT::Activator\n"
                               "Starting NEEDROOT::Activator\n"
                               "Starting DEP2::Activator\n"
                               "Stopping DEP2::Activator\n"
                               "Stopping NEEDROOT::Activator\n"
                               "Stopping ROOT::Activator\n");
        CPPUNIT_ASSERT_MESSAGE("Unexpected message on error log",
                               dynamic_cast<std::ostringstream&>(lMgr->context()->err()).str().empty());
    }


    /*! @test @par Checking failed loading, because of a missing dependency.
        Loads a few bundles.@n
        One of them lacks one dependency : its loading fails.
     */
    void Bundles::checkingMissingDependencies()
    {
        installBundle("fr.osgi.test.root_1.0.0.bndl");
        installBundle("fr.osgi.test.needroot_1.0.0.bndl");
        installBundle("fr.osgi.test.missdep_1.0.0.bndl");

        auto    lMgr = OSGi::BundleMgr::make({ "cleanCache", "verbose", "out=string", "err=string" });
        lMgr->loadBundles();
        lMgr->startBundles();

        CHECK_BUNDLE(           lMgr, "fr.osgi.test.root",     "1.0.0")
        CHECK_BUNDLE(           lMgr, "fr.osgi.test.needroot", "1.0.0")
        CHECK_UNRESOLVED_BUNDLE(lMgr, "fr.osgi.test.missdep")

        lMgr->finalize();
        removeBundles();

        CPPUNIT_ASSERT_MESSAGE("Unexpected message on output log",
                               dynamic_cast<std::ostringstream&>(lMgr->context()->out()).str() ==
                               "Bundle bundles/fr.osgi.test.missdep_1.0.0.bndl installed\n"
                               "Bundle bundles/fr.osgi.test.needroot_1.0.0.bndl installed\n"
                               "Bundle bundles/fr.osgi.test.root_1.0.0.bndl installed\n"
                               "Bundle fr.osgi.test.needroot resolved\n"
                               "Starting ROOT::Activator\n"
                               "Starting NEEDROOT::Activator\n"
                               "Stopping NEEDROOT::Activator\n"
                               "Stopping ROOT::Activator\n");
        CPPUNIT_ASSERT_MESSAGE("Unexpected message on error log",
                               dynamic_cast<std::ostringstream&>(lMgr->context()->err()).str() ==
                               "Missing dependency : fr.osgi.test.r00t 0.0.0\n"
                               "Unresolved bundles : {\n"
                               "    fr.osgi.test.missdep\n"
                               "}\n");
    }


    /*! @test @par Checking failed loading, because of a missing (versioned) dependency.
        Loads a few bundles.@n
        One of them has a dependency, with a version range.@n
        The requested bundle is present, but the version doesn't match : the loading fails.
     */
    void Bundles::checkingDependenciesMissingVersion()
    {
        installBundle("fr.osgi.test.root_1.0.0.bndl");
        installBundle("fr.osgi.test.missver_1.0.0.bndl");

        auto    lMgr = OSGi::BundleMgr::make({ "cleanCache", "verbose", "out=string", "err=string" });
        lMgr->loadBundles();
        lMgr->startBundles();

        CHECK_BUNDLE(           lMgr, "fr.osgi.test.root", "1.0.0")
        CHECK_UNRESOLVED_BUNDLE(lMgr, "fr.osgi.test.missver")

        lMgr->finalize();
        removeBundles();

        CPPUNIT_ASSERT_MESSAGE("Unexpected message on output log",
                               dynamic_cast<std::ostringstream&>(lMgr->context()->out()).str() ==
                               "Bundle bundles/fr.osgi.test.missver_1.0.0.bndl installed\n"
                               "Bundle bundles/fr.osgi.test.root_1.0.0.bndl installed\n"
                               "Bundle fr.osgi.test.root resolved\n"
                               "Starting ROOT::Activator\n"
                               "Stopping ROOT::Activator\n");
        CPPUNIT_ASSERT_MESSAGE("Unexpected message on error log",
                               dynamic_cast<std::ostringstream&>(lMgr->context()->err()).str() ==
                               "Missing dependency : fr.osgi.test.root (1.0.0,2.0.0)\n"
                               "Unresolved bundles : {\n"
                               "    fr.osgi.test.missver\n"
                               "}\n");
    }


    /*!
     */
    void Bundles::checkingWrongDependencies()
    {
        installBundle("fr.osgi.test.need1st_1.0.0.bndl");
        installBundle("fr.osgi.test.zefirst_1.0.0.bndl");

        auto    lMgr = OSGi::BundleMgr::make({ "cleanCache", "verbose", "out=string", "err=string" });
        lMgr->loadBundles();
        lMgr->startBundles();

        lMgr->finalize();
        removeBundles();

        CPPUNIT_ASSERT_MESSAGE("Unexpected message on output log",
                               dynamic_cast<std::ostringstream&>(lMgr->context()->out()).str() ==
                               "Bundle bundles/fr.osgi.test.need1st_1.0.0.bndl installed\n"
                               "Bundle bundles/fr.osgi.test.zefirst_1.0.0.bndl installed\n"
                               "Bundle fr.osgi.test.need1st resolved\n"
                               "Bundle fr.osgi.test.zefirst resolved\n"
                               "Starting LAST::Activator\n"
                               "Starting ZE1ST::Activator\n"
                               "Stopping ZE1ST::Activator\n"
                               "Stopping LAST::Activator\n");
        CPPUNIT_ASSERT_MESSAGE("Unexpected message on error log",
                               dynamic_cast<std::ostringstream&>(lMgr->context()->err()).str().empty());
    }


    /*! @test @par Checking correct unloading, with inter-bundle dependencies (library-level).
        An instance of a derived class is stored in the bundle providing the base class.@n
        While finalizing, the "base" activator deletes the derived instance, before its bundle is uninstalled.
        @note This test has been added after a bugfix (158:34cf4217c10c)
     */
    void Bundles::checkingUnloading()
    {
        installBundle("fr.osgi.test.base_1.0.0.bndl");
        installBundle("fr.osgi.test.derived_1.0.0.bndl");

        auto    lMgr = OSGi::BundleMgr::make({ "cleanCache", "verbose", "out=string", "err=string" });
        lMgr->loadBundles();
        lMgr->startBundles();

        lMgr->finalize();
        removeBundles();

        CPPUNIT_ASSERT_MESSAGE("Unexpected message on output log",
                               dynamic_cast<std::ostringstream&>(lMgr->context()->out()).str() ==
                               "Bundle bundles/fr.osgi.test.base_1.0.0.bndl installed\n"
                               "Bundle bundles/fr.osgi.test.derived_1.0.0.bndl installed\n"
                               "Bundle fr.osgi.test.base resolved\n"
                               "Bundle fr.osgi.test.derived resolved\n"
                               "Starting BASE::Activator\n"
                               "C::C()\n"
                               "Starting DERIVED::Activator\n"
                               "C::C()\n"
                               "D::D()\n"
                               "Stopping DERIVED::Activator\n"
                               "Stopping BASE::Activator\n"
                               "D::~D()\n"
                               "C::~C()\n"
                               "C::~C()\n");
        CPPUNIT_ASSERT_MESSAGE("Unexpected message on error log",
                               dynamic_cast<std::ostringstream&>(lMgr->context()->err()).str().empty());
    }


    /*! @test @par Checking "hot" start (automatic and manual).
        Starts 2 bundles normally, deploys one more, and starts again : only the new one is started.@n
        Starts 2 bundles normally, deploys one more, and manually starts the new one.@n
        In both cases, stopping order is checked.
     */
    void Bundles::checkingDoubleStartAndFinalize()
    {
        {
            removeBundles();
            installBundle("fr.osgi.test.root_1.0.0.bndl");
            installBundle("fr.osgi.test.needroot_1.0.0.bndl");

            auto    lMgr = OSGi::BundleMgr::make({ "cleanCache", "verbose", "out=string", "err=string" });
            lMgr->loadBundles();
            lMgr->startBundles();

            CHECK_BUNDLE(lMgr, "fr.osgi.test.root",        "1.0.0")
            CHECK_BUNDLE(lMgr, "fr.osgi.test.needroot",    "1.0.0")

            auto    lInstaller = lMgr->services()->findByTypeAndName<OSGi::BundleInstallerService>("osgi.core.installer");
            CPPUNIT_ASSERT_MESSAGE("Bundle installer service is missing",
                                   lInstaller);

            auto    lPath = lMgr->properties()->get("application.dir") + "/../bundles/";
            auto    lBndl = lInstaller->installBundle(lPath + "fr.osgi.test.dep2ndlevel_1.0.0.bndl");
            lMgr->startBundles();

            CHECK_BUNDLE(lMgr, "fr.osgi.test.dep2ndlevel", "1.0.0")

            lMgr->finalize();
            removeBundles();

            CPPUNIT_ASSERT_MESSAGE("Unexpected message on output log",
                                   dynamic_cast<std::ostringstream&>(lMgr->context()->out()).str() ==
                                   "Bundle bundles/fr.osgi.test.needroot_1.0.0.bndl installed\n"
                                   "Bundle bundles/fr.osgi.test.root_1.0.0.bndl installed\n"
                                   "Bundle fr.osgi.test.needroot resolved\n"
                                   "Starting ROOT::Activator\n"
                                   "Starting NEEDROOT::Activator\n"
                                   "Bundle fr.osgi.test.dep2ndlevel resolved\n"
                                   "Starting DEP2::Activator\n"
                                   "Stopping DEP2::Activator\n"
                                   "Stopping NEEDROOT::Activator\n"
                                   "Stopping ROOT::Activator\n");
            CPPUNIT_ASSERT_MESSAGE("Unexpected message on error log",
                                   dynamic_cast<std::ostringstream&>(lMgr->context()->err()).str() ==
                                   "Bundle fr.osgi.test.root not started because already in state active\n"
                                   "Bundle fr.osgi.test.needroot not started because already in state active\n");
        }

        {
            removeBundles();
            installBundle("fr.osgi.test.root_1.0.0.bndl");
            installBundle("fr.osgi.test.needroot_1.0.0.bndl");

            auto    lMgr = OSGi::BundleMgr::make({ "cleanCache", "verbose", "out=string", "err=string" });
            lMgr->loadBundles();
            lMgr->startBundles();

            CHECK_BUNDLE(lMgr, "fr.osgi.test.root",        "1.0.0")
            CHECK_BUNDLE(lMgr, "fr.osgi.test.needroot",    "1.0.0")

            auto    lInstaller = lMgr->services()->findByTypeAndName<OSGi::BundleInstallerService>("osgi.core.installer");
            CPPUNIT_ASSERT_MESSAGE("Bundle installer service is missing",
                                   lInstaller);

            auto    lPath = lMgr->properties()->get("application.dir") + "/../bundles/";
            auto    lBndl = lInstaller->installBundle(lPath + "fr.osgi.test.dep2ndlevel_1.0.0.bndl");
            lBndl->act(OSGi::kResolve, lMgr->context());
            lBndl->act(OSGi::kStart, lMgr->context());

            CHECK_BUNDLE(lMgr, "fr.osgi.test.dep2ndlevel", "1.0.0")

            lMgr->finalize();
            removeBundles();

            CPPUNIT_ASSERT_MESSAGE("Unexpected message on output log",
                                   dynamic_cast<std::ostringstream&>(lMgr->context()->out()).str() ==
                                   "Bundle bundles/fr.osgi.test.needroot_1.0.0.bndl installed\n"
                                   "Bundle bundles/fr.osgi.test.root_1.0.0.bndl installed\n"
                                   "Bundle fr.osgi.test.needroot resolved\n"
                                   "Starting ROOT::Activator\n"
                                   "Starting NEEDROOT::Activator\n"
                                   "Starting DEP2::Activator\n"
                                   "Stopping DEP2::Activator\n"
                                   "Stopping NEEDROOT::Activator\n"
                                   "Stopping ROOT::Activator\n");
            CPPUNIT_ASSERT_MESSAGE("Unexpected message on error log",
                                   dynamic_cast<std::ostringstream&>(lMgr->context()->err()).str().empty());
        }
    }


    /*! @test Checking several bundle managers coexistence.
        Two managers are run, one after the other.@n
        Standard tests are run :
        - Using two different cache folders,
        - Using the same cache folders.
     */
    void Bundles::checkingSeveralManagers()
    {
        // Nominal : 2 different cache folders.
        {
            installBundle("fr.osgi.test.alone_1.0.0.bndl");
            auto    lMgr1 = OSGi::BundleMgr::make({ "cleanCache", "verbose", "out=string", "err=string" });
            lMgr1->loadBundles();
            lMgr1->startBundles();
            CHECK_BUNDLE(lMgr1, "fr.osgi.test.alone", "1.0.0")

            installBundle("fr.osgi.test.1st_1.2.3.bndl");
            auto    lMgr2 = OSGi::BundleMgr::make({ "cacheDir=cache2", "verbose", "out=string", "err=string" });
            lMgr2->loadBundles();
            lMgr2->startBundles();
            CHECK_BUNDLE(lMgr2, "fr.osgi.test.alone", "1.0.0")
            CHECK_BUNDLE(lMgr2, "fr.osgi.test.1st",   "1.2.3")

            lMgr2->finalize();
            lMgr1->finalize();
            removeBundles();

            CPPUNIT_ASSERT_MESSAGE("Unexpected message on output log",
                                   dynamic_cast<std::ostringstream&>(lMgr1->context()->out()).str() ==
                                   "Bundle bundles/fr.osgi.test.alone_1.0.0.bndl installed\n"
                                   "Bundle fr.osgi.test.alone resolved\n"
                                   "Starting ALONE::Activator\n"
                                   "Stopping ALONE::Activator\n");
            CPPUNIT_ASSERT_MESSAGE("Unexpected message on error log",
                                   dynamic_cast<std::ostringstream&>(lMgr1->context()->err()).str().empty());
            CPPUNIT_ASSERT_MESSAGE("Unexpected message on output log",
                                   dynamic_cast<std::ostringstream&>(lMgr2->context()->out()).str() ==
                                   "Bundle bundles/fr.osgi.test.1st_1.2.3.bndl installed\n"
                                   "Bundle bundles/fr.osgi.test.alone_1.0.0.bndl installed\n"
                                   "Bundle fr.osgi.test.1st resolved\n"
                                   "Bundle fr.osgi.test.alone resolved\n"
                                   "Starting N1ST::Activator\n"
                                   "Starting ALONE::Activator\n"
                                   "Stopping ALONE::Activator\n"
                                   "Stopping N1ST::Activator\n");
            CPPUNIT_ASSERT_MESSAGE("Unexpected message on error log",
                                   dynamic_cast<std::ostringstream&>(lMgr2->context()->err()).str().empty());
        }
        // Same cache : works as well.
        {
            installBundle("fr.osgi.test.alone_1.0.0.bndl");
            auto    lMgr1 = OSGi::BundleMgr::make({ "cleanCache", "verbose", "out=string", "err=string" });
            lMgr1->loadBundles();
            lMgr1->startBundles();
            CHECK_BUNDLE(lMgr1, "fr.osgi.test.alone", "1.0.0")

            installBundle("fr.osgi.test.1st_1.2.3.bndl");
            auto    lMgr2 = OSGi::BundleMgr::make({ "verbose", "out=string", "err=string" });
            lMgr2->loadBundles();
            lMgr2->startBundles();

            lMgr1->finalize();
            lMgr2->finalize();
            removeBundles();

            CPPUNIT_ASSERT_MESSAGE("Unexpected message on output log",
                                   dynamic_cast<std::ostringstream&>(lMgr1->context()->out()).str() ==
                                   "Bundle bundles/fr.osgi.test.alone_1.0.0.bndl installed\n"
                                   "Bundle fr.osgi.test.alone resolved\n"
                                   "Starting ALONE::Activator\n"
                                   "Stopping ALONE::Activator\n");
            CPPUNIT_ASSERT_MESSAGE("Unexpected message on error log",
                                   dynamic_cast<std::ostringstream&>(lMgr1->context()->err()).str().empty());
            CPPUNIT_ASSERT_MESSAGE("Unexpected message on output log",
                                   dynamic_cast<std::ostringstream&>(lMgr2->context()->out()).str() ==
                                   "Bundle bundles/fr.osgi.test.1st_1.2.3.bndl installed\n"
                                   "Bundle bundles/fr.osgi.test.alone_1.0.0.bndl installed\n"
                                   "Bundle fr.osgi.test.1st resolved\n"
                                   "Bundle fr.osgi.test.alone resolved\n"
                                   "Starting N1ST::Activator\n"
                                   "Starting ALONE::Activator\n"
                                   "Stopping ALONE::Activator\n"
                                   "Stopping N1ST::Activator\n");
            CPPUNIT_ASSERT_MESSAGE("Unexpected message on error log",
                                   dynamic_cast<std::ostringstream&>(lMgr2->context()->err()).str().empty());
        }
    }


    /*! @test @par Checking mode verbose off.
        Disables traces, runs a test usually outputting traces on normal and error logs, and checks output streams are empty.
     */
    void Bundles::checkingVerboseOff()
    {
        // Same test as checkingMissingDependencies.
        installBundle("fr.osgi.test.root_1.0.0.bndl");
        installBundle("fr.osgi.test.needroot_1.0.0.bndl");
        installBundle("fr.osgi.test.missdep_1.0.0.bndl");

        auto    lMgr = OSGi::BundleMgr::make({ "cleanCache", "out=string", "err=string" });
        lMgr->loadBundles();
        lMgr->startBundles();

        CHECK_BUNDLE(           lMgr, "fr.osgi.test.root",     "1.0.0")
        CHECK_BUNDLE(           lMgr, "fr.osgi.test.needroot", "1.0.0")
        CHECK_UNRESOLVED_BUNDLE(lMgr, "fr.osgi.test.missdep")

        lMgr->finalize();
        removeBundles();

        CPPUNIT_ASSERT_MESSAGE("Unexpected message on output log",
                               dynamic_cast<std::ostringstream&>(lMgr->context()->out()).str() ==
                               "Starting ROOT::Activator\n"
                               "Starting NEEDROOT::Activator\n"
                               "Stopping NEEDROOT::Activator\n"
                               "Stopping ROOT::Activator\n");
        CPPUNIT_ASSERT_MESSAGE("Unexpected message on error log",
                               dynamic_cast<std::ostringstream&>(lMgr->context()->err()).str().empty());
    }


    /*! @test @par Checking working directory change during execution.
        Changes current directory before deploying the bundles, which aren't found.@n
        Changes current directory after deploying the bundles, and creating manager : everything is fine.
     */
    void Bundles::checkingPathChange()
    {
        // Just to make sure the right path is restored in case of errors.
        class PathRestore
        {
        public:
            PathRestore(std::string pPath)
                : mPath(pPath)
            { }
            ~PathRestore()
            {
                // Event if an exception is thrown by the assertion, it's still fine, because chdir
                // shall not fail at this point. If it does, just terminating is a valid option.
                if (chdir(mPath.c_str()) != 0) { CPPUNIT_ASSERT_MESSAGE("chdir(path) failed", false); }
            }
        private:
            std::string mPath;
        };
        auto    lWD = OSGi::workingDirectory();

        // Check behavior when config file isn't found.
        try
        {
            PathRestore lFD{lWD};
            if (chdir("../..") != 0) { CPPUNIT_ASSERT_MESSAGE("chdir(../..) failed", false); }

            installBundle("fr.osgi.test.alone_1.0.0.bndl");
            CPPUNIT_ASSERT_MESSAGE("Shoudln't have reached this line", false);
        }
        catch(std::runtime_error& pE)
        {
            CPPUNIT_ASSERT_MESSAGE("Unexpected exception",
                                    std::string(pE.what()) == "Couldn't open input file ../bundles/fr.osgi.test.alone_1.0.0.bndl");
        }

        // Check bundle loading after directory change.
        try
        {
            PathRestore lFD{lWD};
            installBundle("fr.osgi.test.alone_1.0.0.bndl");

            auto    lMgr = OSGi::BundleMgr::make({ "cleanCache", "verbose", "out=string", "err=string" });

            if (chdir("..") != 0) { CPPUNIT_ASSERT_MESSAGE("chdir(..) failed", false); }

            lMgr->loadBundles();
            lMgr->startBundles();

            CHECK_BUNDLE(lMgr, "fr.osgi.test.alone", "1.0.0")

            lMgr->finalize();
        }
        catch(...)
        {
            std::cout << "removeBundles from " << OSGi::workingDirectory();//removeBundles();
            throw;
        }
        removeBundles();
    }


    /*! @test @par Checking failure when giving an invalid bundle path.
        The @ref OSGi_BundleMgr_Args_Page "bundleDir" option misspells the bundle folder : the bundle loading fails.
     */
    void Bundles::checkingInvalidBundlePath()
    {
        std::string lPath{"../binbundles"};
        auto        lMgr = OSGi::BundleMgr::make({ "cleanCache", "verbose", "out=string", "err=string", "bundleDir=" + lPath });

        try
        {
            lMgr->loadBundles();
        }
        catch(OSGi::IOError& pE)
        {
            CPPUNIT_ASSERT_MESSAGE("Unexpected exception",
                                   std::string(pE.what()) == "Can't access bundle folder " + OSGi::workingDirectory() + lPath);
            throw;
        }
    }
}
